package mols;

import javax.microedition.location.AddressInfo;
import javax.microedition.location.Coordinates;
import javax.microedition.location.Criteria;
import javax.microedition.location.Location;
import javax.microedition.location.LocationException;
import javax.microedition.location.LocationProvider;
import javax.microedition.location.Orientation;
import javax.microedition.location.QualifiedCoordinates;
import org.json.me.JSONArray;
import org.json.me.JSONException;
import org.json.me.JSONObject;

/**
 * Questa classe contiene alcune funzioni di utilit&aacute; per 
 * l'inizializzazione e l'uso delle Location API, e per calcoli che coinvolgono
 * distanze e punti geografici.
 */
public class GeoUtils {

    // ------------ alcune costanti per sveltire il calcolo ------------------
    // approssimazione in metri del raggio terrestre
    private static double EARTH_RADIUS = 6372795.4776;
    // costante di conversione da angoli decimali a radianti
    private static double DEG_TO_RAD = Math.PI/180;
    // altre costanti utilizzate nei metodi della classe
    private static double K1 = DEG_TO_RAD * EARTH_RADIUS;
    private static double K1SQUARE = K1 * K1;
    private static double K2 = 1/K1;

    // istanza di LocationProvider usata nell'applicazione
    private static LocationProvider provider = null;

    /**
     * Inizializzaza il LocationProvider, e restituisce <code>false</code>
     * se l'inizializzazione non è andata a buon fine.
     */
    private static boolean initializeLocationProvider(){
        Criteria cr = new Criteria();
        cr.setHorizontalAccuracy(500);
        cr.setPreferredPowerConsumption(Criteria.NO_REQUIREMENT);
        try {
            provider = LocationProvider.getInstance(cr);
        } catch (LocationException ex) { return false; }
        return true;
    }

    /**
     * Calcola il punto geografico che rappresenta il centro della ricerca
     * per i punti di interesse.
     *
     * @param myPos posizione attuale del mobile, incapsulata in un oggetto
     *      <code>Coordinates</code>.
     * @param airDistance distanza in linea d'aria, misurata in metri, del
     *      mobile dal centro di ricerca.
     * @param toFill oggetto <code>Coordinates</code> i cui campi saranno
     *      settati con le coordinate geografiche del centro di ricerca
     *      calcolato.
     *
     * @throws LocationException se l'orientamento del terminale non &eacute;
     *      ricavabile correttamente.
     */
    public static void getSearchCentre(
            Coordinates myPos,
            double airDistance,
            Coordinates toFill) throws LocationException {

        double lat = myPos.getLatitude();
        double lng = myPos.getLongitude();

        if (airDistance > 0){
            Orientation o = Orientation.getOrientation();
            double angle = o.getCompassAzimuth() * DEG_TO_RAD;
            lat = airDistance * Math.cos(angle) * K2 + myPos.getLatitude();
            lng = airDistance * Math.sin(angle) * K2 + myPos.getLongitude();
        }
        
        toFill.setLatitude(lat);
        toFill.setLongitude(lng);
    }

    /**
     * Costruisce un oggetto POI a partire dalla stringa JSON restituita
     * dal servizio Local Search di Google.
     *
     * @param jsonPOI l'oggetto <code>JSONObject</code> che incapsula un punto 
     *      di interesse come stringa JSON.
     *
     * @return un oggetto <code>POI</code> che incapsula le informazioni sul
     *      punto di interesse.
     *
     * @throws JSONException se si verificano dei problemi durante il parsing
     *      della stringa JSON.
     */
    public static POI buildPOIFromGoogleJSON(JSONObject jsonPOI)
            throws JSONException{

        String name = jsonPOI.getString("titleNoFormatting");
        String description = "Not Available";

        double lat = Double.parseDouble(jsonPOI.getString("lat"));
        double lng = Double.parseDouble(jsonPOI.getString("lng"));
        QualifiedCoordinates qc = new QualifiedCoordinates
                (lat, lng, Float.NaN, Float.NaN, Float.NaN);
        AddressInfo ai = new AddressInfo();
        ai.setField(AddressInfo.CITY, jsonPOI.getString("city"));
        ai.setField(AddressInfo.COUNTRY, jsonPOI.getString("country"));
        ai.setField(AddressInfo.STREET, jsonPOI.getString("streetAddress"));

        String phone = "Non disponibile";
        try{
            JSONArray phones = jsonPOI.getJSONArray("phoneNumbers");
            if (phones.length() > 0){
                phone = phones.getJSONObject(0).getString("number");
            }
        }catch(Exception e){}
        ai.setField(AddressInfo.PHONE_NUMBER, phone);

        POI poi = new POI(name, description, qc, ai);
        return poi;
    }

    /**
     * Verifica che un punto geografico <code>p</code> sia in un certo raggio
     * rispetto ad un altro punto geografico <code>centre</code>.
     *
     * @param centre il punto geografico di cui sopra, incapsulato in un oggetto
     *      <code>Coordinates</code>.
     * @param radius il raggio, espresso in metri, di un cerchio che ha come
     *      centro il punto <code>centre</code>.
     * @param p il punto geografico di cui sopra, incapsulato in un oggetto
     *      <code>Coordinates</code>.
     *
     * @return <code>true</code> se il punto <code>p</code> &eacute; in un
     *      raggio di <code>radius</code> metri dal punto <code>centre</code>.
     */
    public static double isInRadius(Coordinates centre, double radius,
        Coordinates p){
        double dist = distanceBetween(centre, p);
        return ((radius == -1) ? dist : ((dist <= radius)? dist : -1));
    }

    /**
     * Calcola un'approssimazione in metri della distanza tra due punti
     * geografici.
     *
     * @param c1 il primo punto geografico, incapsulato in un oggetto
     *      <code>Coordinates</code>.
     * @param c2 il secondo punto geografico, incapsulato in un oggetto
     *      <code>Coordinates</code>.
     *
     * @return la distanza in metri, approssimata, tra <code>c1</code> e
     *      <code>c2</code>.
     */
    public static double distanceBetween(Coordinates c1, Coordinates c2){
        double diff1 = c1.getLatitude() - c2.getLatitude();
        diff1 = diff1 * diff1;
        double diff2 = c1.getLongitude() - c2.getLongitude();
        diff2 = diff2 * diff2;
        double d2 = (diff1 + diff2) * K1SQUARE;
        return Math.sqrt(d2);
    }

    /**
     * Ottiene la posizione geografica del terminale per mezzo delle
     * Location API.
     *
     * @param un oggetto <code>Coordinates</code> i cui campi saranno riempiti
     *      con le coordinate geografiche dell'ubicazione del mobile.
     *
     * @return <code>true</code> se la posizione del mobile &eacute; stata
     *      correttamente ottenuta, <code>false</code> altrimenti.
     */
    public static boolean getMyPosition(Coordinates toFill) {
        if(provider == null)
            initializeLocationProvider();
        
        try {
            Location myLocation = provider.getLocation(600);
            if (myLocation.isValid()){
                Coordinates point = myLocation.getQualifiedCoordinates();
                toFill.setLatitude(point.getLatitude());                
                toFill.setLongitude(point.getLongitude());                
            }
        } catch(Exception e){ return false; }
        return true;
    }
}
